/******************************************************************************
 * This program is a 100% Java Email Server.
 ******************************************************************************
 * Copyright (c) 2001-2011, Eric Daugherty (http://www.ericdaugherty.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************
 * For current versions and more information, please visit:
 * http://javaemailserver.sf.net/
 *
 * or contact the authors at:
 * java@ericdaugherty.com
 * andreaskyrmegalos@hotmail.com
 *
 ******************************************************************************
 * This program is based on the CSRMail project written by Calvin Smith.
 * http://crsemail.sourceforge.net/
 ******************************************************************************
 *
 * $Rev$
 * $Date$
 *
 ******************************************************************************/

package com.ericdaugherty.mail;

//Java import
import java.io.*;
import java.util.ArrayList;
import java.util.List;

//Local import
import com.ericdaugherty.mail.server.services.general.StreamHandler;
import com.ericdaugherty.mail.server.services.smtp.MIMEProcessor;
import com.ericdaugherty.mail.server.services.smtp.support.AddDataLine;


/**
 *
 * This class processes the data in the same manner as the SMTPProcessor
 * handles the data stream after receiving the DATA command.
 *
 * @author Andreas Kyrmegalos
 */
public class PseudoSMTPProcessor extends MIMEProcessor {

   private static final byte[] EOL;
   static {
      byte[] line_separator = null;
      try {
         line_separator = System.getProperty("line.separator").getBytes(US_ASCII);
      } catch (UnsupportedEncodingException ex) {
         line_separator = System.getProperty("line.separator").getBytes();
      } finally {
         EOL = line_separator;
      }
   }
   
   private File outputFile;
   private byte[] output;
   private int finalBufferSize = 32;
   private boolean endOfMessage = false;
   private BasicStreamHandler smtpSH;

   private final class BasicStreamHandler extends StreamHandler {

      public void setStreams(InputStream is, OutputStream os) throws IOException {
         if (outputStream == null&&os!=null) {
            outputStream = os;
            activeOutputStream = new BufferedOutputStream(outputStream, 4096);
         }
         if (inputStream == null&&is!=null) {
            inputStream = new BufferedInputStream(is);
            inputReader = new BufferedReader(new InputStreamReader(inputStream, US_ASCII));
            activeInputStream = inputStream;
         }
      }
   }

   private final class TestAddDataLine implements AddDataLine {

      private List stringLines = new ArrayList(250);

      public void addDataLine(byte[] line) throws IOException {
         stringLines.add(line);
         if (stringLines.size() == 250) {
            saveIncrement(stringLines, true);
            stringLines.clear();
         }
      }

      public void flush() throws IOException {
         if (stringLines.size() > 0) {
            saveIncrement(stringLines, true);
         }
         stringLines.clear();
         stringLines = null;
      }
   }

   //This is called (multiple times) while persisting a file.
   private void saveIncrement(List<byte[]> dataLines, boolean append) throws IOException {
      int length = dataLines.size();
      BufferedOutputStream bos = null;
      try {
         bos = new BufferedOutputStream(new FileOutputStream(outputFile, append));
         for (int index = 0; index < length; index++) {
            bos.write(dataLines.get(index));
            bos.write(EOL);
         }
      } catch (FileNotFoundException e) {
      } catch (UnsupportedEncodingException e) {/*Safely ignore, US-ASCII isn't going anywhere*/

      } finally {
         if (null != bos) {
            try {
               bos.close();
            } catch (IOException e) {
               e.printStackTrace();
            }
            bos = null;
         }
      }

   }

   public void process(String filename, String domain) {

      setDomain(domain);

      outputFile = new File(filename+".converted");

      addDataLine = new TestAddDataLine();
      mimeBody = new MIMEBody(null);
      
      try {

         smtpSH = new BasicStreamHandler();
         smtpSH.setStreams(new FileInputStream(new File(filename)), (OutputStream)null);
         readOnceInputStream();
         if (!endOfMessage) {
            for (;;) {
               readInputStream();
               if (endOfMessage) {
                  break;
               }
            }
         }
         try {
            if (endOfMessage) {
               addDataLine.flush();
            }

         } catch (Exception e) {
            e.printStackTrace();
         }
      } catch (IOException e) {
         e.printStackTrace();
      }
      finally {
         if (smtpSH != null) {
            try {
               smtpSH.getActiveInputStream().close();
            } catch (IOException ex) {}
         }
      }
   }

   private int lastByteInLatestBuffer = 0x00;

   private void readOnceInputStream() throws IOException {

      int currentRead = -1;
      byte[] buffer = new byte[finalBufferSize + 1];
      currentRead = smtpSH.read(buffer, 0, finalBufferSize);
      if (currentRead == -1) {
         return;
      }

      //Check that there is a 0x0d before each 0x0a. If not add it.
      //This is necessary since MIMEProcessor expects data to respect
      //the 0x0d,0x0a line termination. *nix OSes store data using 0x0a
      //as a line separator unlike windows which uses 0x0d,0x0a. Macs
      //prefer the 0x0d flavor. JES 2.1.0 has not been tested with any
      //OSX systems and incompatibilities are almost certainly present.
      byte aByte;
      int newCurrentRead = currentRead;
      for (int i=0;i<newCurrentRead;i++) {
         aByte = buffer[i];
         if (aByte==0x0a) {

            if (i>0&&buffer[i-1]==0x0d) continue;
            if (i>0||lastByteInLatestBuffer!=0x0d) {
               newCurrentRead++;
               if (buffer.length<newCurrentRead+1) {
                  byte[] newBuffer = new byte[buffer.length+1];
                  for (int j=0;j<=i;j++) {
                     newBuffer[j] = buffer[j];
                  }
                  for (int j=newCurrentRead-1;j>=i;j--) {
                     newBuffer[j+1] = buffer[j];
                  }
                  buffer = newBuffer;
               }
               else {
                  for (int j=newCurrentRead-1;j>=i;j--) {
                     buffer[j+1] = buffer[j];
                  }
               }
               buffer[i] = 0x0d;
               i++;
            }
         }
      }
      if (newCurrentRead>0) {
         lastByteInLatestBuffer = buffer[newCurrentRead-1];
      }
      else {
         lastByteInLatestBuffer = 0x00;
      }
      currentRead = newCurrentRead;

      constructLineOfText(currentRead, buffer);
   }

   private void readInputStream() throws IOException {

      int currentRead = -1;
      byte[] buffer = new byte[finalBufferSize + 1];
      currentRead = smtpSH.read(buffer, 0, finalBufferSize);
      if (currentRead == -1) {
         return;
      }

      //Check that there is a 0x0d before each 0x0a. If not add it.
      //This is necessary since MIMEProcessor expects data to respect
      //the 0x0d,0x0a line termination. *nix OSes store data using 0x0a
      //as a line separator unlike windows which uses 0x0d,0x0a. Macs
      //prefer the 0x0d flavor. JES 2.x.0 has not been tested with any
      //OSX systems and incompatibilities are almost certainly present.
      byte aByte;
      int newCurrentRead = currentRead;
      for (int i=0;i<newCurrentRead;i++) {
         aByte = buffer[i];
         if (aByte==0x0a) {

            if (i>0&&buffer[i-1]==0x0d) continue;
            if (i>0||lastByteInLatestBuffer!=0x0d) {
               newCurrentRead++;
               if (buffer.length<newCurrentRead+1) {
                  byte[] newBuffer = new byte[buffer.length+1];
                  for (int j=0;j<=i;j++) {
                     newBuffer[j] = buffer[j];
                  }
                  for (int j=newCurrentRead-1;j>=i;j--) {
                     newBuffer[j+1] = buffer[j];
                  }
                  buffer = newBuffer;
               }
               else {
                  for (int j=newCurrentRead-1;j>=i;j--) {
                     buffer[j+1] = buffer[j];
                  }
               }
               buffer[i] = 0x0d;
               i++;
            }
         }
      }
      if (newCurrentRead>0) {
         lastByteInLatestBuffer = buffer[newCurrentRead-1];
      }
      else {
         lastByteInLatestBuffer = 0x00;
      }
      currentRead = newCurrentRead;

      constructLineOfText(currentRead, buffer);
   }

   private void constructLineOfText(int currentRead, byte[] buffer) throws IOException {

      int nextByte = -1, previousRead = 0;
      for (int i = 0; i < currentRead; i++) {
         if (buffer[i] == 0x0d || buffer[i] == 0x0a) {
            if (i + 1 == currentRead) {
               nextByte = smtpSH.read();
               lastByteInLatestBuffer = nextByte;
               if (nextByte != -1) {
                  buffer[i + 1] = (byte) nextByte;
                  i = constructLineOfText3(buffer, true, previousRead, i);
                  if (endOfMessage) {
                     return;
                  }
               } //Perhaps a truncated end of DATA transmission. Check if it so.
               else if (output == null && i - previousRead == 1 && buffer[previousRead] == 0x2E) {
                  endOfMessage = true;
                  return;
               }
               break;
            } else {
               i = constructLineOfText3(buffer, false, previousRead, i);
               if (endOfMessage) {
                  return;
               }
               previousRead = i + 1;
            }
         }
         if (i == currentRead - 1) {
            output = constructLineOfText2(output, buffer, previousRead, currentRead);
            finalBufferSize *= 2;
         }
      }
   }

   private byte[] constructLineOfText2(byte[] output, byte[] buffer, int startSegmentCount, int currentSegmentCount) {

      if (output != null) {
         int tempOutputLength;
         byte[] tempOutput;
         tempOutputLength = output.length;
         tempOutput = new byte[tempOutputLength];
         System.arraycopy(output, 0, tempOutput, 0, tempOutputLength);
         output = new byte[tempOutputLength + currentSegmentCount - startSegmentCount];
         System.arraycopy(tempOutput, 0, output, 0, tempOutputLength);
         System.arraycopy(buffer, startSegmentCount, output, tempOutputLength, currentSegmentCount - startSegmentCount);
      } else {
         output = new byte[currentSegmentCount - startSegmentCount];
         System.arraycopy(buffer, startSegmentCount, output, 0, currentSegmentCount - startSegmentCount);
      }
      return output;
   }

   private int constructLineOfText3(byte[] buffer, boolean increment, int previousRead, int i) throws IOException {

      if (buffer[i + 1] == 0x0a) {
         i++;
         output = constructLineOfText2(output, buffer, previousRead, i - 1);
         int outputLength = output.length;
         if (outputLength > 16 && outputLength <= 128) {
            finalBufferSize = outputLength + 2;
         }
         if (endOfMessage = checkEndOfDATA(output)) {
            return -1;
         }
         processDATA(output);
         if (buffer[i - 1] == 0x0a) {
            processDATA(new byte[0]);
         }
         output = null;
      } else {
         if (increment) {
            i++;
         }
         if (buffer[i] == 0x0a && output != null && output[output.length - 1] == 0x0d) {
            output = constructLineOfText2(output, output, 0, output.length);
            processDATA(output);
            output = null;
         } else {
            output = constructLineOfText2(output, buffer, previousRead, i + 1);
         }
      }
      return i;
   }
}
